package com.panopset.game.blackjack;

import java.io.StringWriter;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

import com.panopset.game.card.Shoe;

/**
 * Blackjack round.
 *
 * @author Karl Dinwiddie
 *
 */
public class Cycle {

    @Override
    public String toString() {
        StringWriter sw = new StringWriter();
        sw.append("Dealer: ");
        if (dealer != null) {
            sw.append("" + dealer.cards);
        }
        if (players != null) {
            int i = 0;
            for (Player p : players) {
                sw.append("Player " + i++);
                sw.append(": ");
                sw.append(p.toString());
            }
        }
        sw.append(" Bankroll:" + m.getBankroll());
        return sw.toString();
    }

    private final BlackjackConfig c;

    private final BlackjackGame m;

    private final Shoe z;

    private Dealer dealer;

    private List<Player> players;

    public Dealer getDealer() {
        if (dealer == null) {
            dealer = new Dealer();
        }
        return dealer;
    }

    public List<Player> getPlayers() {
        if (players == null) {
            players = new CopyOnWriteArrayList<Player>();
            for (int i = 0; i < c.getSeats(); i++) {
                int betAmount = m.getNextBet();
                if (c.isCountVeryPositive()) {
                    betAmount = m.getConfig().getLargeBet();
                }
                if (c.isCountVeryNegative()) {
                    m.shuffle();
                }
                players.add(new Player(betAmount, c, m.getBankroll()));
            }
        }
        return players;
    }

    public Player getActivePlayer() {
        for (Player p : getPlayers()) {
            if (!p.isFinal()) {
                return p;
            }
        }
        return null;
    }

    Cycle(final BlackjackGame game, final Shoe shoe) {
        synchronized (game) {
            c = game.getConfig();
            m = game;
            z = shoe;
        }
    }

    public RecommendedAction getRecommendedAction() {
        if (getDealer().isFinal() || !getDealer().hasCards()) {
            return new RecommendedAction(BlackjackGame.CMD_DEAL, "L");
        } else {
            Player p = getActivePlayer();
            if (p == null) {
                return new RecommendedAction(BlackjackGame.CMD_DEAL, "L");
            }
            Hand h = p.getActiveHand();
            if (h == null) {
                return new RecommendedAction(BlackjackGame.CMD_DEAL, "L");
            }
            return getStrategy().getRecommendation(
                    new Situation(c, getDealer().getUpCard(), h));
        }
    }

    public String getStrategyLine() {
        Player p = getActivePlayer();
        if (p == null) {
            return BlackjackGame.CMD_DEAL;
        }
        Hand h = p.getActiveHand();
        if (h == null) {
            return BlackjackGame.CMD_DEAL;
        }
        return getStrategy().findStrategyLine(
                new Situation(c, getDealer().getUpCard(), h)).getSource();
    }

    private Strategy s;

    public Strategy getStrategy() {
        if (s == null) {
            s = new Strategy(c);
        }
        return s;
    }

    public void deal() {
        if (c.isBasicStrategyVariationsOnly() && !m.isAutomatic()) {
            m.startFullAutomaticFromVariations();
            return;
        }
        synchronized (m) {
            for (Player p : getPlayers()) {
                p.getActiveHand().deal(new BlackjackCard(z.deal()));
                m.reportNewHand();
            }
            getDealer().deal(new BlackjackCard(z.deal(), false));
            for (Player p : getPlayers()) {
                p.getActiveHand().deal(new BlackjackCard(z.deal()));
                if (p.getActiveHand().getValue() == 21) {
                    p.getActiveHand().stand();
                }
            }
            getDealer().deal(new BlackjackCard(z.deal()));
            if (getDealer().getValue() == 21) {
                finish();
            }
        }
        dealt = true;
    }

    public Hand getActivePlayerHand() {
        Player p = getActivePlayer();
        if (p == null) {
            return null;
        }
        return p.getActiveHand();
    }

    public synchronized void finish() {
        synchronized (m) {

            Double blackjackPayoff = 2.5;

            if (m.getConfig().isBlackjack6to5()) {
                blackjackPayoff = 2.2;
            }

            if (getDealer().isFinal()) {
                return;
            }
            boolean playersAllHaveBlackjack = true;
            boolean playersAreAllResolved = true;
            for (Player p : getPlayers()) {
                for (Hand h : p.getAllHands()) {
                    if (h.isDone()) {
                        playersAllHaveBlackjack = false;
                    } else {
                        playersAreAllResolved = false;
                        if (getDealer().isNatural21() && !c.isEuropeanStyle()) {
                            h.stand();
                        }
                        if (!h.isNatural21()) {
                            playersAllHaveBlackjack = false;
                        }
                    }
                }
            }
            if (playersAllHaveBlackjack || playersAreAllResolved) {
                getDealer().stand();
                getDealer().setShowing(0);
            } else if (getDealer().isInitialDeal()) {
                getDealer().setShowing(0);
                while (!getDealer().isFinal()) {
                    if (getDealer().getValue() < 17) {
                        getDealer().deal(m.deal());
                    } else if (getDealer().getValue() == 17) {
                        if (c.isDealerHitSoft17() && getDealer().isSoft()) {
                            getDealer().deal(m.deal());
                        } else {
                            getDealer().stand();
                        }
                    } else {
                        getDealer().stand();
                    }
                }
            }
            for (Player p : getPlayers()) {
                for (Hand h : p.getAllHands()) {
                    if (h.isBusted()) {
                        continue;
                    }
                    if (getDealer().isNatural21() && c.isEuropeanStyle()) {
                        h.setMessage(c.getLostMsg());
                        continue;
                    }
                    if (h.isSurrendered()) {
                        m.getBankroll().addChipsInTray((int) (.5 * h.getChips()));
                        continue;
                    }
                    if (h.getValue() == getDealer().getValue()) {
                        if (h.isNaturalNonSplit()) {
                            if (getDealer().isNatural21()) {
                                h.setMessage(c.getPushMsg());
                                m.getBankroll().addChipsInTray(h.getChips());
                            } else {
                                h.setMessage(c.getBlackjackMsg());
                                m.getBankroll().addChipsInTray(
                                        (int) (blackjackPayoff * h.getChips()));
                            }
                        } else {
                            if (h.isNatural21()) {
                                if (getDealer().isNatural21()) {
                                    h.setMessage(c.getPushMsg());
                                    m.getBankroll().addChipsInTray(h.getChips());
                                } else {
                                    h.setMessage(c.getWonMsg());
                                    m.getBankroll().addChipsInTray(2 * h.getChips());
                                }
                            } else {
                                h.setMessage(c.getPushMsg());
                                m.getBankroll().addChipsInTray(h.getChips());
                            }
                        }
                    } else if (getDealer().getValue() > 21
                            || h.getValue() > getDealer().getValue()) {
                        if (h.isNaturalNonSplit()) {
                            h.setMessage(c.getBlackjackMsg());
                            m.getBankroll().addChipsInTray(
                                    (int) (blackjackPayoff * h.getChips()));
                        } else {
                            h.setMessage(c.getWonMsg());
                            m.getBankroll().addChipsInTray(2 * h.getChips());
                        }
                    } else {
                        h.setMessage(c.getLostMsg());
                    }
                }
            }
            for (Player p : getPlayers()) {
                for (Hand h : p.getAllHands()) {
                    h.clearHandChips();
                }
            }
        }
        active = false;
    }

    private boolean dealt = false;

    private boolean active = true;

    public synchronized boolean isActive() {
        return active;
    }

    public boolean isDealt() {
        return dealt;
    }

    public synchronized void setNextBet(int value, Bankroll bankroll) {
        if (value < 1) {
            return;
        }
        for (Player p : getPlayers()) {
            for (Hand h : p.getAllHands()) {
                if (h.isEmpty()) {
                    h.setHandChips(value);
                }
            }
        }
    }
}
